// $Id: ASTUtil.java 10000 2006-06-08 21:04:45Z steve.ebersole@jboss.com $
package org.makumba.providers.query.mql;

import org.hibernate.hql.antlr.HqlTokenTypes;

import antlr.ASTFactory;
import antlr.collections.AST;
import antlr.collections.impl.ASTArray;

/** Makumba adaptation of the original class in org.hibernate.hql.ast.util. 
 * To update, check the differences with the .original file in this repository 
 * */
/**
 * Provides utility methods for AST traversal and manipulation.
 * 
 * @author Joshua Davis (pgmjsd@sourceforge.net)
 */
public final class ASTUtil {
    /**
     * Private empty constructor. (or else checkstyle says: 'warning: Utility classes should not have a public or
     * default constructor.')
     * 
     * @deprecated (tell clover to ignore this)
     */
    @Deprecated
    private ASTUtil() {
    }

    /**
     * Creates a single node AST.
     * 
     * @param astFactory
     *            The factory.
     * @param type
     *            The node type.
     * @param text
     *            The node text.
     * @return AST - A single node tree.
     */
    public static AST create(ASTFactory astFactory, int type, String text) {
        AST node = astFactory.create(type, text);
        return node;
    }

    /**
     * Creates a single node AST as a sibling.
     * 
     * @param astFactory
     *            The factory.
     * @param type
     *            The node type.
     * @param text
     *            The node text.
     * @param prevSibling
     *            The previous sibling.
     * @return AST - A single node tree.
     */
    public static AST createSibling(ASTFactory astFactory, int type, String text, AST prevSibling) {
        AST node = astFactory.create(type, text);
        node.setNextSibling(prevSibling.getNextSibling());
        prevSibling.setNextSibling(node);
        return node;
    }

    public static AST insertSibling(AST node, AST prevSibling) {
        node.setNextSibling(prevSibling.getNextSibling());
        prevSibling.setNextSibling(node);
        return node;
    }

    /**
     * Creates a 'binary operator' subtree, given the information about the parent and the two child nodex.
     * 
     * @param factory
     *            The AST factory.
     * @param parentType
     *            The type of the parent node.
     * @param parentText
     *            The text of the parent node.
     * @param child1
     *            The first child.
     * @param child2
     *            The second child.
     * @return AST - A new sub-tree of the form "(parent child1 child2)"
     */
    public static AST createBinarySubtree(ASTFactory factory, int parentType, String parentText, AST child1, AST child2) {
        ASTArray array = createAstArray(factory, 3, parentType, parentText, child1);
        array.add(child2);
        return factory.make(array);
    }

    /**
     * Creates a single parent of the specified child (i.e. a 'unary operator' subtree).
     * 
     * @param factory
     *            The AST factory.
     * @param parentType
     *            The type of the parent node.
     * @param parentText
     *            The text of the parent node.
     * @param child
     *            The child.
     * @return AST - A new sub-tree of the form "(parent child)"
     */
    public static AST createParent(ASTFactory factory, int parentType, String parentText, AST child) {
        ASTArray array = createAstArray(factory, 2, parentType, parentText, child);
        return factory.make(array);
    }

    public static AST createTree(ASTFactory factory, AST[] nestedChildren) {
        AST[] array = new AST[2];
        int limit = nestedChildren.length - 1;
        for (int i = limit; i >= 0; i--) {
            if (i != limit) {
                array[1] = nestedChildren[i + 1];
                array[0] = nestedChildren[i];
                factory.make(array);
            }
        }
        return array[0];
    }

    /**
     * Finds the first node of the specified type in the chain of children.
     * 
     * @param parent
     *            The parent
     * @param type
     *            The type to find.
     * @return The first node of the specified type, or null if not found.
     */
    public static AST findTypeInChildren(AST parent, int type) {
        AST n = parent.getFirstChild();
        while (n != null && n.getType() != type) {
            n = n.getNextSibling();
        }
        return n;
    }

    /**
     * Returns the last direct child of 'n'.
     * 
     * @param n
     *            The parent
     * @return The last direct child of 'n'.
     */
    public static AST getLastChild(AST n) {
        return getLastSibling(n.getFirstChild());
    }

    /**
     * Returns the last sibling of 'a'.
     * 
     * @param a
     *            The sibling.
     * @return The last sibling of 'a'.
     */
    private static AST getLastSibling(AST a) {
        AST last = null;
        while (a != null) {
            last = a;
            a = a.getNextSibling();
        }
        return last;
    }

    /**
     * Returns the 'list' representation with some brackets around it for debugging.
     * 
     * @param n
     *            The tree.
     * @return The list representation of the tree.
     */
    public static String getDebugString(AST n) {
        StringBuffer buf = new StringBuffer();
        buf.append("[ ");
        buf.append(n == null ? "{null}" : n.toStringTree());
        buf.append(" ]");
        return buf.toString();
    }

    /**
     * Find the previous sibling in the parent for the given child.
     * 
     * @param parent
     *            the parent node
     * @param child
     *            the child to find the previous sibling of
     * @return the previous sibling of the child
     */
    public static AST findPreviousSibling(AST parent, AST child) {
        AST prev = null;
        AST n = parent.getFirstChild();
        while (n != null) {
            if (n == child) {
                return prev;
            }
            prev = n;
            n = n.getNextSibling();
        }
        throw new IllegalArgumentException("Child not found in parent!");
    }

    /**
     * Determine if a given node (test) is a direct (throtle to one level down) child of another given node (fixture).
     * 
     * @param fixture
     *            The node against which to testto be checked for children.
     * @param test
     *            The node to be tested as being a child of the parent.
     * @return True if test is contained in the fixtures's direct children; false otherwise.
     */
    public static boolean isDirectChild(AST fixture, AST test) {
        AST n = fixture.getFirstChild();
        while (n != null) {
            if (n == test) {
                return true;
            }
            n = n.getNextSibling();
        }
        return false;
    }

    /**
     * Determine if a given node (test) is contained anywhere in the subtree of another given node (fixture).
     * 
     * @param fixture
     *            The node against which to testto be checked for children.
     * @param test
     *            The node to be tested as being a subtree child of the parent.
     * @return True if child is contained in the parent's collection of children.
     */
    public static boolean isSubtreeChild(AST fixture, AST test) {
        AST n = fixture.getFirstChild();
        while (n != null) {
            if (n == test) {
                return true;
            }
            if (n.getFirstChild() != null && isSubtreeChild(n, test)) {
                return true;
            }
            n = n.getNextSibling();
        }
        return false;
    }

    /**
     * Makes the child node a sibling of the parent, reconnecting all siblings.
     * 
     * @param parent
     *            the parent
     * @param child
     *            the child
     */
    public static void makeSiblingOfParent(AST parent, AST child) {
        AST prev = findPreviousSibling(parent, child);
        if (prev != null) {
            prev.setNextSibling(child.getNextSibling());
        } else { // child == parent.getFirstChild()
            parent.setFirstChild(child.getNextSibling());
        }
        child.setNextSibling(parent.getNextSibling());
        parent.setNextSibling(child);
    }

    public static String getPathText(AST n) {
        StringBuffer buf = new StringBuffer();
        getPathText(buf, n);
        return buf.toString();
    }

    private static void getPathText(StringBuffer buf, AST n) {
        AST firstChild = n.getFirstChild();
        // If the node has a first child, recurse into the first child.
        if (firstChild != null) {
            getPathText(buf, firstChild);
        }
        // Append the text of the current node.
        buf.append(n.getText());
        // If there is a second child (RHS), recurse into that child.
        if (firstChild != null && firstChild.getNextSibling() != null) {
            getPathText(buf, firstChild.getNextSibling());
        }
    }

    public static boolean hasExactlyOneChild(AST n) {
        return n != null && n.getFirstChild() != null && n.getFirstChild().getNextSibling() == null;
    }

    public static void appendSibling(AST n, AST s) {
        while (n.getNextSibling() != null) {
            n = n.getNextSibling();
        }
        n.setNextSibling(s);
    }

    /**
     * Inserts the child as the first child of the parent, all other children are shifted over to the 'right'.
     * 
     * @param parent
     *            the parent
     * @param child
     *            the new first child
     */
    public static void insertChild(AST parent, AST child) {
        if (parent.getFirstChild() == null) {
            parent.setFirstChild(child);
        } else {
            AST n = parent.getFirstChild();
            parent.setFirstChild(child);
            child.setNextSibling(n);
        }
    }

    /**
     * Filters nodes out of a tree.
     */
    public static interface FilterPredicate {
        /**
         * Returns true if the node should be filtered out.
         * 
         * @param n
         *            The node.
         * @return true if the node should be filtered out, false to keep the node.
         */
        boolean exclude(AST n);
    }

    /**
     * A predicate that uses inclusion, rather than exclusion semantics.
     */
    public abstract static class IncludePredicate implements FilterPredicate {
        @Override
        public final boolean exclude(AST node) {
            return !include(node);
        }

        public abstract boolean include(AST node);
    }

    private static ASTArray createAstArray(ASTFactory factory, int size, int parentType, String parentText, AST child1) {
        ASTArray array = new ASTArray(size);
        array.add(factory.create(parentType, parentText));
        array.add(child1);
        return array;
    }

    public static Node makeNode(int type, String string) {
        Node node = new Node();
        node.setType(type);
        node.setText(string);
        return node;
    }

    /**
     * Given a DOT tree, construct the path as String
     * 
     * @param onlyFirstChild
     *            TODO
     */
    public static String constructPath(AST type) {
        String a_text = "", b_text = "";
        AST a = type.getFirstChild();
        if (a == null) {
            return type.getText();
        }

        AST b = a.getNextSibling();

        if (a.getType() == HqlSqlTokenTypes.DOT) {
            a_text = constructPath(a);
        } else {
            a_text = a.getText();
        }
        if (b == null) {
            return a_text;
        }
        if (b.getType() == HqlSqlTokenTypes.DOT) {
            b_text = constructPath(b);
        } else {
            b_text = b.getText();
        }
        return a_text + "." + b_text;
    }

    /**
     * Given a path of the kind a.b.c, constructs a DOT tree AST
     */
    public static AST constructPath(ASTFactory fact, String path) {
        if (path.indexOf(".") > -1) {
            String a = path.substring(0, path.indexOf("."));
            AST d = ASTUtil.create(fact, HqlSqlTokenTypes.DOT, ".");
            AST i = ASTUtil.create(fact, HqlSqlTokenTypes.IDENT, a);
            d.setFirstChild(i);
            i.setNextSibling(constructPath(fact, path.substring(path.indexOf(".") + 1)));
            return d;

        } else {
            return ASTUtil.create(fact, HqlSqlTokenTypes.IDENT, path);
        }
    }

    /**
     * Given an AST, return the path, by concatenating all child ASTs that have dots
     */
    public static String getPath(AST t) {
        if (t.getType() == HqlTokenTypes.DOT) {
            return constructPath(t);
        } else {
            return t.getText();
        }
    }

}
